﻿using System;
using System.Collections.Generic;
using System.Collections;
using linq = Microsoft.Linq.Expressions;
using Bling.Util;
using Bling.DSL;
using Bling.Core;
using Bling.Properties;
namespace Bling.Reactive {
  // NEW EVENT STREAM IDEA.
  // (a) register a virtual action.
  // (b) the action is "compiled" for the event, an activator is returned.
  // (c) "activate" the subscription, get a handle that must be pinned

  public interface ISubscribeHandle {
    /// <summary>
    /// cancle the subscription.
    /// </summary>
    void Cancel();
  }

  public abstract class EventStream {
    // fix api. 
    // one: use weak references
    // two: better prepare subscriptions. 
    // three: indirection.
    public Func<ISubscribeHandle> PrepareSubscribe(Action a) { throw new NotSupportedException(); }


    public abstract Action Subscribe(Action a);
    public Action SubscribeForever(Action a) {
      Action StreamFire = null;
      Action StreamUns = null;
      EventStream Stream = this;
      StreamFire = () => {
        if (StreamFire != null) {
          a();
          StreamUns = Subscribe(StreamFire);
        }
      };
      StreamUns = Subscribe(StreamFire);
      return () => {
        if (StreamUns != null) StreamUns();
        //xxx();
        StreamFire = null;
      };

    }
    public static EventStream operator &(EventStream Stream, BoolBl Guard) {
      return new AndEventStream() { Stream = Stream, Guard = Guard };
    }

    public static EventStream operator &(BoolBl Guard, EventStream Stream) {
      return new And2EventStream() { Stream = Stream, Guard = Guard };
    }
    public static EventStream operator |(EventStream StreamA, EventStream StreamB) {
      return new OrEventStream() { Streams = new EventStream[] { StreamA, StreamB } };
    }
    private class AndEventStream : EventStream {
      internal EventStream Stream;
      internal BoolBl Guard;
      internal Func<bool> GuardF;
      public override Action Subscribe(Action a) {
        if (GuardF == null) {
          GuardF = Guard.AsFunc;
        }
        //var GuardF = (((object)this.Guard) != null) ? Guard.AsFunc : this.GuardF;
        Action aa = null;
        aa = () => {
          if (GuardF()) a();
          // re-subscribe!
          else Stream.Subscribe(aa);
        };
        return Stream.Subscribe(aa);
      }
      public override string ToString() {
        return Stream + " & " + Guard;
      }
    }
    private class And2EventStream : EventStream {
      internal EventStream Stream;
      internal BoolBl Guard;
      private Func<bool> GuardF;
      public override Action Subscribe(Action a) {
        if (GuardF == null) {
          GuardF = Guard.AsFunc;
        }
        Action GuardChanged = null;
        Action GuardUns = null;
        Action StreamFired = null;
        Action StreamUns = null;
        GuardChanged = () => {
          if (GuardChanged == null) return;
          if (GuardF()) {
            if (StreamUns == null) 
              StreamUns = Stream.Subscribe(StreamFired);
          } else if (StreamUns != null) {
            StreamUns();
            StreamUns = null;
          }
          GuardUns = Guard.Changed.Subscribe(GuardChanged);
        };
        StreamFired = () => {
          if (StreamFired == null) return;
          if (!GuardF()) return; // maybe we fired on accident?
          // unarm the gaurd.
          if (GuardUns != null) {
            GuardUns();
            GuardUns = null;
          }
          StreamUns = null;
          a();
        };
        GuardChanged();
        return () => {
          if (StreamUns != null) StreamUns();
          if (GuardUns != null) GuardUns();
          StreamUns = null;
          GuardUns = null;
          GuardChanged = null; StreamFired = null;
        };
      }
      public override string ToString() {
        return Guard + " & " + Stream ;
      }
    }
    internal class OrEventStream : EventStream {
      internal EventStream[] Streams;
      public override Action Subscribe(Action a) {
        var uns = new Action[Streams.Length];
        for (int i = 0; i < Streams.Length; i++) {
          int j = i;
          uns[j] = Streams[j].Subscribe(() => {
            a();
            for (int k = 0; k < uns.Length; k++)
              if (k != j && uns[k] != null) uns[k]();
          });
        }
        return () => {
          for (int k = 0; k < uns.Length; k++)
            if (uns[k] != null) uns[k]();
        };
      }
      public override string ToString() {
        var str = Streams[0].ToString();
        for (int k = 1; k < Streams.Length; k++)
          str = str + " | " + Streams[k].ToString();
        return str;
      }
    }
    public static readonly DefaultEventStream<Action> Null = new DefaultEventStream<Action>() {
      Transform = a => a,
      Add = a => { },
      Remove = a => { },
    };

  }
  public class ExplicitEventStream : EventStream {
    public List<Action> Listeners;
    public override Action Subscribe(Action a) {
      Listeners.Add(a);
      return () => { Listeners.Remove(a); };
    }
  }

  public class BasicEventStream : EventStream {
    public Func<Action, Action> Subscribe0;
    public override Action Subscribe(Action a) {
      return Subscribe0(a);
    }
  }
  public class DefaultEventStream<TDelegate> : EventStream {
    public DefaultEventStream() { }
    public Func<Action, TDelegate> Transform { set; private get; }
    public Action<TDelegate> Add { set; private get; }
    public Action<TDelegate> Remove { set; private get; }
    public override Action Subscribe(Action a) {
      Action aa = null;
      TDelegate aaa = default(TDelegate);
      aa = () => {
        Remove(aaa);
        a();
      };
      aaa = Transform(aa);
      Add(aaa);
      return () => Remove(aaa);
    }
  }
}
namespace Bling.Reactive {

  public abstract class Command<COMMAND> where COMMAND : Command<COMMAND> {
    private EventStream When;
    private bool Forever0;
    private readonly string Name;
    protected Command(COMMAND self) { this.Name = self.Name; this.Forever0 = self.Forever0; this.When = self.When; }

    public Command(string Name) {
      this.Name = Name;
    }
    public override string ToString() {
      return Name + (Forever0 ? ".Forever" : "") + (When != null ? "[" + When + "]" : "");
    }
    protected Action DoActivate(Action a) {
      if (When == null) {
        a();
        return null;
      } else if (Forever0) return When.SubscribeForever(a);
      else return When.Subscribe(a);
    }
    protected abstract COMMAND Copy();
    public COMMAND this[EventStream When] {
      get {
        (this.When == null).Assert();
        var ret = Copy();
        ret.When = When;
        return ret;
      }
    }
    public COMMAND Forever {
      get {
        var ret = Copy();
        ret.Forever0 = true;
        return ret;
      }
    }
  }
  public class Command : Command<Command> {
    private readonly Action Action;
    protected Command(Command self) : base(self) { this.Action = self.Action;  }
    public Command(string name, Action a) : base( name) { this.Action = a; }
    protected override Command Copy() { return new Command(this); }
    public Action Apply() {
      return DoActivate(Action);
    }

  }
}
namespace Bling.Reactive {
  public abstract class BaseState {
    /// <summary>
    /// Fires when this state is entered.
    /// </summary>
    public abstract EventStream In { get; }
    /// <summary>
    /// Fires when this state is exited.
    /// </summary>
    public abstract EventStream Out { get; }
    /// <summary>
    /// Fires when this state is transitioned to, whether from outside or itself.
    /// </summary>
    public abstract EventStream Update { get; }
  }

  public abstract class MultiState<T,BRAND> : BaseState where BRAND : Brand<T,BRAND> {
    protected abstract T Target0 { get; }
    /// <summary>
    /// The item that is the target of this state. 
    /// This is only valid during the events of this state. 
    /// </summary>
    public BRAND Target {
      get {
        return Brand<T, BRAND>.State(() => Target0);
      }
    }
    public override EventStream Out {
      get { return new BasicEventStream() { Outer = this, Original = Out0, }; }
    }
    public override EventStream Update {
      get { return new BasicEventStream() { Outer = this, Original = Update0, }; }
    }
    private class BasicEventStream : EventStream {
      public MultiState<T, BRAND> Outer;
      public EventStream Original;
      public override Action Subscribe(Action a) {
        Action aa = null;
        Action aaa = null;
        var at = Outer.Target0;
        aa = () => {
          var at0 = Outer.Target0;
          if (!at.Equals(at0)) { // not us, go again.
            aaa = Original.Subscribe(aa);
            return;
          }
          a();
          return;
        };
        aaa = Original.Subscribe(aa);
        return aaa == (null) ? ((Action) null) : () => {
          aaa();
        };
      }
    }


    protected abstract EventStream Update0 { get; }
    protected abstract EventStream Out0 { get; }
    /// <summary>
    /// Get state of a specific target value. 
    /// </summary>
    public State this[BRAND Target] {
      /// <summary>
      /// Get state of a specific target value. 
      /// </summary>
      get { return new AtomState() { Target = Target, Outer = this }; }
    }
    public abstract Func<bool> IsCurrentState(BRAND Value);
    private class AtomState : State {
      internal MultiState<T,BRAND> Outer;
      internal BRAND Target;
      public override EventStream In {
        get { return Outer.In & Outer.Target == Target; }
      }
      public override EventStream Out {
        get { return Outer.Out0 & Outer.Target == Target; }
      }
      public override EventStream Update {
        get { return Outer.Update0 & Outer.Target == Target; }
      }
      private Func<bool> IsCurrentState0;
      public override Func<bool> IsCurrentState {
        get { 
          if (IsCurrentState0 == null) 
            IsCurrentState0 = Outer.IsCurrentState(Target);
          return IsCurrentState0;
        }
      }
      public override string ToString() {
        return Outer.ToString() + "[" + Target + "]";
      }
    }
  }

  public abstract class State : BaseState {
    public abstract Func<bool> IsCurrentState { get; }
    public static State operator !(State state) {
      return new NotState() { Original = state };
    }
    private List<ExplicitClient> Listeners = null;
    public static implicit operator BoolBl(State state) { return state.ToBool; }
    public BRAND Condition<BRAND>(IConditionTarget<BRAND> ArgA, BRAND ArgB) where BRAND : IConditionTarget<BRAND> {
      return ToBool.Condition(ArgA, ArgB);
    }
    // any state can become a boolean
    public virtual BoolBl ToBool {
      get {
        return new ExplicitPropertyExpr<bool>((e) => {
          if (Listeners == null) Listeners = new List<ExplicitClient>();
          var oldCount = Listeners.Count;
          Listeners.Add(e);
          if (oldCount != 0) return;

          Action OnEnter = null;
          Action OnExit = () => {
            if (Listeners.Update()) // go again!
              In.Subscribe(OnEnter);
          };
          OnEnter = () => {
            if (Listeners.Update())
              Out.Subscribe(OnExit);
          };
          if (IsCurrentState()) {
            Out.Subscribe(OnExit);
          } else In.Subscribe(OnEnter);
        }, () => {
          var ret = IsCurrentState();
          return ret;
        }, In | Out);
      }
    }


    private class NotState : State {
      public State Original;
      public override EventStream In { get { return Original.Out; } }
      public override EventStream Out { get { return Original.In; } }
      public override EventStream Update { get { return EventStream.Null; } }
      public override string ToString() { return "!" + Original; }
      public override Func<bool> IsCurrentState {
        get { return () => !Original.IsCurrentState(); }
      }
      public override BoolBl ToBool {
        get { return !Original.ToBool; }
      }
    }
  }
  public class Machine {
    public LocalState Initial { get { return States[0]; } }
    private int CurrentState = -1;
    private int Phase = 0;
    private readonly List<LocalState> States = new List<LocalState>();

    internal struct Transition {
      public EventStream When;
      public int Destination;
      public void Trigger() {

      }
    }

    public class LocalState : State {
      public string Name;
      internal Machine Outer;
      internal int Index;
      internal readonly List<Transition> Transitions = new List<Transition>();
      internal int Epsilon0 = -1;
      internal readonly List<Action> Enter0 = new List<Action>();
      internal readonly List<Action> Exit0 = new List<Action>();
      internal readonly List<Action> Update0 = new List<Action>();
      
      public LocalState Epsilon {
        set {
          (Transitions.Count == 0).Assert();
          (Epsilon0 < 0).Assert();
          Epsilon0 = value.Index;
        }
      }
      public LocalState this[EventStream When] {
        set {
          Transition(When, value);
        }
      }

      public LocalState Transition(EventStream When, LocalState To) {
        (Outer.CurrentState < 0).Assert(); // machine is not active.
        (Epsilon0 < 0).Assert();
        Transitions.Add(new Transition() {
          When = When, Destination = To.Index,
        });
        return this;
      }
      private static EventStream GetEvents(List<Action> Listeners) {
        return new ExplicitEventStream() { Listeners = Listeners };
      }
      public override EventStream In { get { return GetEvents(Enter0); } }
      public override EventStream Out { get { return GetEvents(Exit0); } }
      public override EventStream Update { get { return GetEvents(Update0); } }
      public override Func<bool> IsCurrentState {
        get { return () => Outer.CurrentState == Index; }
      }
      public override string ToString() {
        return Outer.ToString() + "." + (Name != null ? Name : Index.ToString());
      }
    }
    public override string ToString() {
      return (Name != null) ? Name : base.ToString();
    }
    public string Name;
    public Machine() {
      var Initial = new LocalState() { Outer = this, Name = "Initial", Index = 0 };
      States.Add((LocalState)Initial);
    }
    public LocalState MakeState() {
      var state = new LocalState() { Outer = this, Index = States.Count };
      States.Add(state);
      return state;
    }
    public LocalState MakeState(string Name) {
      var state = MakeState();
      state.Name = Name;
      return state;
    }
    public void Activate() {
      (CurrentState < 0).Assert();
      CurrentState = 0;
      Enter(true);
    }
    protected void Enter(bool DoEnter) {
      var At = States[this.CurrentState];
      if (DoEnter) At.Enter0.Flush();
      At.Update0.Flush();

      Phase += 1;
      var AtPhase = Phase;
      if (At.Epsilon0 >= 0) {
        (At.Transitions.Count == 0).Assert();
        At.Exit0.Flush();
        ChangeTo(At.Epsilon0);
        return;
      }
      List<Action> Unsubscribe = null;
      foreach (var t in At.Transitions) {
        var e = t.When;
        var NextState = t.Destination;
        var uns = e.Subscribe(() => {
          if (Phase != AtPhase) return;
          else {
            if (Unsubscribe != null) {
              foreach (var a in Unsubscribe) a();
              Unsubscribe = null;
            }
            ChangeTo(NextState);
          }
        });
        if (uns == null) continue;
        if (Unsubscribe == null) Unsubscribe = new List<Action>();
        Unsubscribe.Add(uns);
      }
    }
    private void ChangeTo(int State) {
      //Console.WriteLine("CHANG:\t" + CurrentState + " to " + State);
      if (State != CurrentState) {
        //Console.WriteLine("FEXIT:\t" + At.Exit0.Count);
        var OldAt = CurrentState;
        CurrentState = State;
        States[OldAt].Exit0.Flush();
        Enter(true);
      } else if (State == CurrentState) {
        Enter(false);
      }
    }
  }
  public struct ExplicitClient {
    public Action Update;
    public Func<bool> IsExpired;
    public bool DoUpdate() {
      if (IsExpired()) return false;
      Update();
      return true;
    }
  }
  public interface ExplicitPropertyEval<EVAL> where EVAL : Eval<EVAL> {
    Eval<EVAL>.Info<T> PropertyValue<T>(ExplicitPropertyExpr<T> Expr);
  }
  public class ExplicitPropertyExpr<T> : Expr<T>, ICanChangeExpr<T> {
    private readonly Action<ExplicitClient> NotifyAdded;
    public readonly Func<T> ComputeT;
    public ExplicitPropertyExpr(Action<ExplicitClient> NotifyAdded, Func<T> ComputeT, EventStream Changed) {
      this.NotifyAdded = NotifyAdded; this.ComputeT = ComputeT; this.Changed = Changed;
    }
    public EventStream Changed { get; private set; }

    public void OnBind(ExplicitClient e) {
      NotifyAdded(e);
    }
    public Eval<EVAL>.Info<T> StraightEval<EVAL>(Eval<EVAL> txt) where EVAL : Eval<EVAL> {
      if (txt is TranslateEval<EVAL>) {
        return ((TranslateEval<EVAL>)txt).NoTranslation(this);
      } else if (txt is Bling.Linq.LinqEval<EVAL>) {
        var txt0 = ((Bling.Linq.LinqEval<EVAL>)txt);
        //var ret = txt0.DoEval(ComputeT);

        var ret = new Bling.Linq.LinqEval<EVAL>.MyInfo<T>() {
          Value = linq.Expression.Invoke(linq.Expression.Constant(ComputeT))
        };
        return ret;
      } else if (txt is IDynamicEval<EVAL>) {
        return ((IDynamicEval<EVAL>)txt).Dynamic<T>(DynamicMode.Dynamic);
      }
      throw new NotSupportedException();
    }

    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is ExplicitPropertyEval<EVAL>)
        return ((ExplicitPropertyEval<EVAL>)txt).PropertyValue(this);
      else return StraightEval(txt);
    }
  }
  // rotate[drag.update].Now = 
  //   mouse3d.AngleBetween(mouse3d[drag.begin]) * rotate[drag.begin];
  //
  // temporary databind.
  // canvas.background[drag] = Brushes.Red;
  public static class ReactiveExtensions {
    public static bool Update(this List<ExplicitClient> Updated) {
      for (int i = 0; i < Updated.Count; ) {
        if (Updated[i].IsExpired()) {
          Updated.RemoveAt(i); continue;
        }
        Updated[i].Update();
        i += 1;
      }
      return Updated.Count > 0;
    }

  }
}

namespace Bling.DSL {
  using Bling.Reactive;
  public interface ICanChangeExpr<T> : IExpr<T> {
    EventStream Changed { get; }
  }
  internal class ChangeVisitor : TranslateEval<ChangeVisitor> {
    public readonly List<EventStream> Streams = new List<EventStream>();
    protected override Expr<T> RealTranslate<T>(Expr<T> value) {
      if (value is ICanChangeExpr<T>)
        Streams.Add(((ICanChangeExpr<T>)value).Changed);
      return base.RealTranslate<T>(value);
    }
  }


  public partial class EventExpr<T> : Expr<T>, IBindableExpr<T> {
    public readonly Expr<T> Underlying;
    public readonly EventStream When;
    public EventExpr(Expr<T> Underlying, EventStream When) {
      this.Underlying = Underlying;
      this.When = When;
    }
    protected override string ToString1() {
      return Underlying + "[" + When + "]";
    }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      var F = Underlying.AsFunc;
      T t = F();
      When.SubscribeForever(() => {
        t = F();
      });
      var e = new Ops.MapOperator<T, T>(t0 => {
        return t;
      }) { CanOptimize = false }.Make(new Constant<T>(F()));
      return txt.DoEval(e);
    }
    private class BindHelper : IAssign {
      public readonly List<Action> Actions = new List<Action>();
      public bool Accept<S>(Expr<S> LHS, Expr<S> RHS) {
        if (LHS is IBindableExpr<S>) {
          Action DoBind;
          if (((IBindableExpr<S>)LHS).DelayBindWith(RHS, out DoBind)) {
            Actions.Add(DoBind);
            return true;
          }
        }
        return false;
      }
      public IAssign Copy() { return this; }
    }

    public bool BindWith(Expr<T> RHS) {
      var helper = new BindHelper();
      if (!Underlying.Solve(helper, RHS)) return false;
      var results = helper.Actions.ToArray();
      (results.Length > 0).Assert();
      When.SubscribeForever(() => {
        foreach (var r in results) r();
      });
      return true;
    }
    // delay twice?
    public bool DelayBindWith(Expr<T> value, out Action DoBind) { DoBind = null; return false; }

    public bool TwoWayBindWith(Expr<T> value) {
      return false;
    }
    public bool BindWith(Expr value) { return BindWith((Expr<T>)value); }
  }
  public abstract partial class Expr : IExpr {
    public EventStream BaseChanged {
      get {
        var txt = new ChangeVisitor();
        txt.DoEval(this);
        if (txt.Streams.Count == 0) return EventStream.Null;
        else if (txt.Streams.Count == 1) return txt.Streams[0];
        else return new EventStream.OrEventStream() { Streams = txt.Streams.ToArray() };
      }
    }
  }

}

namespace Bling.Core {
  using Bling.Reactive;
 
  public partial interface ICanBind<BRAND> : ICoreValue<BRAND> where BRAND : ICanBind<BRAND> {
    BRAND this[EventStream When] { get; set; }
  }
  public abstract partial class Brand<BRAND> : Brand, ICanBind<BRAND>, ICanMakeParam<BRAND> where BRAND : Brand<BRAND> {
    public abstract BRAND this[EventStream Event] { get; set; }
  }
  public abstract partial class Brand<T, BRAND> : Brand<BRAND>, IBrandT<T>, IExtends<BRAND, ObjectBl>, IBrand<T, BRAND> where BRAND : Brand<T, BRAND> {
    /// <summary>
    /// An optional event stream that fires when the value of this brand could have changed. What 
    /// thread the event stream fires in depends on the provider of this brand. For example, a dependency
    /// property provider will fire in the UI thread that owns the dependency property. 
    /// </summary>
    public EventStream Changed {
      get { return Provider.BaseChanged; }
    }

    public override BRAND this[EventStream When] {
      get {
        return CopyMetaTo(ToBrand(new EventExpr<T>(Provider, When)));
      }
      set {
        var assign = new NowAssign<Action>();
        if (Provider.Solve(assign, value.Provider)) {
          // register.
          When.SubscribeForever(() => {
            foreach (var r in assign.Results) r();
          });
          return;
        }
        throw new NotSupportedException("Cannot assign reactive value");
      }
    }
  }
}